package com.semidata.rtd.core.http.client;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.ClientCookieEncoder;
import io.netty.handler.codec.http.Cookie;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.GenericFutureListener;

import java.net.URI;
import java.util.Set;

import com.semidata.rtd.core.service.Service;

public class HttpClient implements Service {

    private static final int TIMEOUT = 1000;
    private EventLoopGroup group = null;
    private int timeout;
    

    public HttpClient() {
        this(TIMEOUT);
    }
    
    public HttpClient(int timeout) {
        this.timeout = timeout;
    }

    @Override
    public void start() {
        group = new NioEventLoopGroup();
    }

    @Override
    public void stop() {
        group.shutdownGracefully();

    }

    private void send(String url, final ByteBuf buffer, final int length, final HttpHeaders headers, final Set<Cookie> cookies, HttpResponseHandler handler, final HttpClientErrorHandler errorHandler,
            final HttpClientCloseHandler closeHandler) {
        ChannelFuture future = null;
        try {
            final URI uri = new URI(url);
            String scheme = uri.getScheme() == null ? "http" : uri.getScheme();
            if (!scheme.equalsIgnoreCase("http"))
                throw new Exception("not support protocol " + scheme);

            final String host = uri.getHost() == null ? "localhost" : uri.getHost();
            final int port = uri.getPort() == -1 ? 80 : uri.getPort();

            Bootstrap b = new Bootstrap();

            b.group(group).channel(NioSocketChannel.class).handler(new HttpClientInitializer(handler, timeout)).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, timeout);

            future = b.connect(host, port);
            future.addListener(new GenericFutureListener<ChannelFuture>() {

                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Channel ch = future.channel();
                    if (ch == null) {
                        if (errorHandler != null)
                            errorHandler.handle(future);
                        return;
                    }

                    ch.closeFuture().addListener(new GenericFutureListener<ChannelFuture>() {

                        @Override
                        public void operationComplete(ChannelFuture future) throws Exception {
                            if (closeHandler != null)
                                closeHandler.handle(future);
                        }

                    });
                    if (future.isSuccess()) {

                        if (ch != null) {
                            String path = uri.getPath();
                            String query = uri.getQuery();
                            if (query != null && !query.isEmpty())
                                path += "?" + query;

                            HttpRequest request;

                            if (buffer == null || length == 0) {
                                request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, path);
                            } else {
                                request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, path, buffer);
                            }

                            if (headers != null) {
                                request.headers().set(headers);
                            }

                            if (!request.headers().contains(HttpHeaders.Names.HOST))
                                request.headers().set(HttpHeaders.Names.HOST, port == 80 ? host : host + ":" + port);
                            if (!request.headers().contains(HttpHeaders.Names.CONNECTION))
                                request.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE);

                            if (!request.headers().contains(HttpHeaders.Names.CONTENT_LENGTH))
                                request.headers().set(HttpHeaders.Names.CONTENT_LENGTH, length);

                            if (cookies != null) {
                                request.headers().set(HttpHeaders.Names.COOKIE, ClientCookieEncoder.encode(cookies));
                            }

                            ch.writeAndFlush(request);
                        }
                    } else {
                        ch.close();
                        if (errorHandler != null)
                            errorHandler.handle(future);
                    }

                }

            });
        } catch (Exception e) {
            if (future != null)
                future.channel().close();
            throw new RuntimeException(e);
        }
    }

    public void send(String url, HttpResponseHandler handler) {
        send(url, null, null, handler, null, null);
    }

    public void send(String url, HttpHeaders headers, Set<Cookie> cookies, HttpResponseHandler handler, HttpClientErrorHandler errorHandler, HttpClientCloseHandler closeHandler) {
        send(url, null, 0, headers, cookies, handler, errorHandler, closeHandler);
    }

    public void send(String url, String data, HttpResponseHandler handler) {
        send(url, data, null, null, handler, null, null);
    }

    public void send(String url, String data, HttpHeaders headers, Set<Cookie> cookies, HttpResponseHandler handler, HttpClientErrorHandler errorHandler, HttpClientCloseHandler closeHandler) {
        ByteBuf buffer = Unpooled.copiedBuffer(data, CharsetUtil.UTF_8);
        send(url, buffer, data.length(), headers, cookies, handler, errorHandler, closeHandler);
    }

    public void send(String url, byte[] data, HttpResponseHandler handler) {
        send(url, data, null, null, handler, null, null);
    }

    public void send(String url, byte[] data, HttpHeaders headers, Set<Cookie> cookies, HttpResponseHandler handler, HttpClientErrorHandler errorHandler, HttpClientCloseHandler closeHandler) {
        ByteBuf buffer = Unpooled.copiedBuffer(data);
        send(url, buffer, data.length, headers, cookies, handler, errorHandler, closeHandler);
    }

}
